梦想不会自己发光,真正闪耀的是那个为梦狂奔的你。献给知行的孩子们!(Eric.He著)
回溯算法的本质:它是一种深度优先搜索(DFS)的暴力枚举策略,核心是“尝试 - 回退”—— 在解决问题的过程中,一步步尝试所有可能的选择,当发现当前选择无法得到合法解时,就回退到上一步,撤销当前选择,继续尝试其他可能,直到找到所有解 / 一个合法解。
问题描述:
算法解析:
和组合总和的核心区别是:排列要求元素顺序不同则为不同解,且每个元素只能用一次,因此需要额外的标记数组记录元素是否已被选取,避免重复使用。
#include <iostream>
#include <vector>
using namespace std;
vector<vector<int>> res_permute; // 存储所有全排列结果
vector<int> path_permute; // 存储当前正在构建的排列路径
vector<bool> used_permute; // 标记数组:used_permute[i]表示第i个元素是否已被选取
// 回溯函数:nums-原数组
void permute(vector<int>& nums) {
// 终止条件:路径长度等于数组长度,找到一个完整排列
if (path_permute.size() == nums.size()) {
res_permute.push_back(path_permute);
return;
}
// 遍历所有元素,尝试选取未被使用的元素
for (int i = 0; i < nums.size(); ++i) {
if (used_permute[i]) continue; // 跳过已被选取的元素,核心去重/防重复使用
used_permute[i] = true; // 标记:选取当前元素
path_permute.push_back(nums[i]);// 加入路径
permute(nums); // 递归:继续构建排列
path_permute.pop_back(); // 回溯:移除最后一个元素
used_permute[i] = false; // 回溯:取消标记,还原状态
}
}
int main() {
vector<int> nums = {1,2,3};
used_permute.resize(nums.size(), false); // 初始化标记数组,全部为未使用
permute(nums);
for(vector<int> item : res_permute)
{
cout << "[";
for(int node : item )
{
cout << " " << node << " ";
}
cout << "]" << endl;
}
return 0;
}
[ 1 2 3 ]
[ 1 3 2 ]
[ 2 1 3 ]
[ 2 3 1 ]
[ 3 1 2 ]
[ 3 2 1 ]
问题描述:
算法解析:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<vector<int>> res; // 存储最终所有有效组合
vector<int> path; // 存储当前正在构建的组合
// 回溯函数:candidates-原数组,target-剩余目标值,start-当前遍历起始索引
void backtrack(vector<int>& candidates, int target, int start) {
// 终止条件1:剩余目标值为0,找到有效组合
if (target == 0) {
res.push_back(path);
return;
}
// 终止条件2:剩余目标值小于0,剪枝(无需继续递归)
if (target < 0) {
return;
}
// 从start开始遍历,避免重复组合
for (int i = start; i < candidates.size(); ++i) {
path.push_back(candidates[i]); // 选择当前元素
// 递归:剩余目标值减去当前元素,起始索引仍为i(可重复选取)
backtrack(candidates, target - candidates[i], i);
path.pop_back(); // 回溯:撤销选择,移除最后一个元素
}
}
int main() {
vector<int> candidates = {2,3,6,7};
int target = 7;
// 可选优化:排序后,若当前元素大于剩余target,可直接break(后续元素更大,无需遍历)
sort(candidates.begin(), candidates.end());
backtrack(candidates, target, 0);
for(vector<int> item : res)
{
cout << "[";
for(int node : item )
{
cout << " " << node << " ";
}
cout << "]" << endl;
}
return 0;
}
[ 2 2 3 ]
[ 7 ]
问题描述:
算法解析:
核心思路和组合总和相似,但终止条件更灵活 ——遍历过程中每一个路径都是有效子集,无需等目标值达标或路径长度满额,直接收集即可。
#include <iostream>
#include <vector>
using namespace std;
vector<vector<int>> res_subsets; // 存储所有子集结果
vector<int> path_subsets; // 存储当前正在构建的子集路径
// 回溯函数:nums-原数组,start-当前遍历的起始索引(核心去重)
void subsets(vector<int>& nums, int start) {
res_subsets.push_back(path_subsets); // 关键:每一步路径都是有效子集,先收集再递归
// 从start开始遍历,避免重复子集
for (int i = start; i < nums.size(); ++i) {
path_subsets.push_back(nums[i]); // 选择:加入当前元素
subsets(nums, i + 1); // 递归:下一个元素从i+1开始(不可重复选)
path_subsets.pop_back(); // 回溯:撤销选择,还原状态
}
}
int main() {
vector<int> nums = {1,2,3};
subsets(nums, 0);
for(vector<int> item : res_subsets)
{
cout << "[";
for(int node : item )
{
cout << " " << node << " ";
}
cout << "]" << endl;
}
return 0;
}
[]
[ 1 ]
[ 1 2 ]
[ 1 2 3 ]
[ 1 3 ]
[ 2 ]
[ 2 3 ]
[ 3 ]